package impl

import (
	"strconv"

	"hundsun.com/hsl/hschain/protos/config"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/common/log"
	"hundsun.com/hsl/hschain/common/types"
	pbcommon "hundsun.com/hsl/hschain/protos/common"
	"hundsun.com/hsl/hschain/protos/consensus/dpos"
	pbledger "hundsun.com/hsl/hschain/protos/ledger"
	"hundsun.com/hsl/hschain/protos/p2p"
	"hundsun.com/hsl/hschain/store/db"
	"hundsun.com/hsl/hschain/store/extend/ledger"
	"hundsun.com/hsl/hschain/store/mq"
)

func (chain *BlockChain) getBlock(msg *mq.Message) {
	requestBlock := (msg.Data).(*pbcommon.ReqBlock)
	block, err := chain.ProcGetBlockMsg(requestBlock)
	if err != nil {
		chain.log.Errorf("ProcGetBlockMsg failed, err: %s", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, block))
	}
}

func (chain *BlockChain) requestBlock(msg *mq.Message) {
	requestBlock := (msg.Data).(*pbcommon.ReqBlocks)
	blocks, err := chain.ProcRequestBlockMsg(requestBlock)
	if err != nil {
		chain.log.Errorf("ProcRequestBlockMsg failed, err: %s", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, blocks))
	}
}

func (chain *BlockChain) getBlocks(msg *mq.Message) {
	requestblocks := (msg.Data).(*pbcommon.ReqBlocks)
	blocks, err := chain.ProcGetBlocksMsg(requestblocks)
	if err != nil {
		chain.log.Errorf("ProcGetBlocksMsg failed, err: %s", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		chain.log.Debugf("ProcGetBlocksMsg success, get %d blocks", len(blocks))
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, blocks))
	}
}

func (chain *BlockChain) getBlockExecResult(msg *mq.Message) {
	requestblock := (msg.Data).(*pbcommon.ReqBlock)
	block, err := chain.ProcGetBlockExecResultMsg(requestblock)
	if err != nil {
		chain.log.Error("ProcGetBlockDetailsMsg", "err", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		//chainchain.log.Debug("ProcGetBlockDetailsMsg", "success", "ok")
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, block))
	}
}

func (chain *BlockChain) getExecutedBlock(msg *mq.Message) {
	requestblock := (msg.Data).(*pbcommon.ReqBlock)
	block, err := chain.ProcGetExecutedBlockMsg(requestblock)
	if err != nil {
		chain.log.Errorf("ProcGetExecutedBlockMsg failed, err:%s\n", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		chain.log.Debugf("ProcGetExecutedBlockMsg success, block height: %d", requestblock.Height)
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, block))
	}
}

func (chain *BlockChain) getExecutedBlocks(msg *mq.Message) {
	requestblock := (msg.Data).(*pbcommon.ReqBlocks)
	blocks, err := chain.ProcGetExecutedBlocksMsg(requestblock)
	if err != nil {
		chain.log.Errorf("ProcGetExecutedBlocksMsg failed, err:%s\n", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, blocks))
	}
}

func (chain *BlockChain) addBlock(msg *mq.Message) {
	blk := (msg.Data).(*pbledger.Block)
	err := chain.ProcAddBlockMsg(blk)

	var replyMsg = &pbcommon.Reply{}
	if err != nil {
		chain.log.Error("ProcAddBlocksMsg", "err", err.Error())
		replyMsg.Msg = []byte(err.Error())
		replyMsg.IsOk = false
	} else {
		blockBytes, err := proto.Marshal(blk)
		if err != nil {
			chain.log.Error("Add block marshal block bytes failed, err: %s\n", err.Error())
			return
		}
		replyMsg.Msg = blockBytes
		replyMsg.IsOk = true
	}

	reply := chain.topicClient.NewMessage(types.ConsensusModule, mq.ModeTopic.String(), types.EventReplyAddBlock, blk)
	err = chain.topicClient.Send(reply, false)
	if err != nil {
		chain.log.Error("Reply event add block failed, err: %s\n", err.Error())
		return
	}
}

func (chain *BlockChain) updatePeerInfo(msg *mq.Message) {
	peerInfos := (msg.Data).(*p2p.PeerInfos)
	err := chain.ProcUpdatePeerInfo(peerInfos)
	if err != nil {
		chain.log.Error("ProcUpdatePeerInfo", "err", err.Error())
	} else {
		chain.log.Debug("ProcUpdatePeerInfo", "success", "ok")
	}
}

func (chain *BlockChain) addBroadcastBlock(msg *mq.Message) {
	blks := (msg.Data).([]*pbledger.Block)
	chain.ProcAddBroadcastBlockMsg(blks)
}

func (chain *BlockChain) saveBlock(msg *mq.Message) {
	blkNum := (msg.Data).(uint64)
	err := chain.ProcSaveBlockMsg(blkNum)

	var replyMsg = &pbcommon.Reply{}
	if err != nil {
		chain.log.Error("ProcSaveBlockMsg", "err", err.Error())
		replyMsg.Msg = []byte(err.Error())
		replyMsg.IsOk = false
	} else {
		replyMsg.IsOk = true
	}

	reply := chain.topicClient.NewMessage(types.ConsensusModule, mq.ModeTopic.String(), types.EventReplySaveBlock, replyMsg)
	err = chain.topicClient.Send(reply, false)
	if err != nil {
		chain.log.Error("Reply event save block failed, err: %s\n", err.Error())
	}
}

func (chain *BlockChain) deletePendingBlock(msg *mq.Message) {
	blkHeight := msg.Data.(uint64)
	chain.DeletePendingBlock(blkHeight)
}

func (chain *BlockChain) getBlockHeight(msg *mq.Message) {
	lastBlock, err := chain.ProcGetLastBlockMsg()
	if err != nil {
		chain.log.Error("ProcGetLastBlockMsg", "err", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		//chainchain.log.Debug("ProcGetBlockDetailsMsg", "success", "ok")
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, lastBlock.Block.Header.Number))
	}
}

func (chain *BlockChain) getLastBlock(msg *mq.Message) {
	block, err := chain.ProcGetLastBlockMsg()
	if err != nil {
		chain.log.Error("ProcGetLastBlockMsg", "err", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		//chainchain.log.Debug("ProcGetBlockDetailsMsg", "success", "ok")
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, block))
	}
}

func (chain *BlockChain) queryTx(msg *mq.Message) {
	req := (msg.Data).(*pbcommon.ReqHashes)
	tx, err := chain.ProcQueryTxMsg(req)
	if err != nil {
		chain.log.Error("ProcAddBlocksMsg", "err", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		//chainchain.log.Debug("ProcGetBlockDetailsMsg", "success", "ok")
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, tx))
	}
}

func (chain *BlockChain) isSynched(msg *mq.Message) {
	var bSynched = false

	if chain.GetCurParam().GetConsensus().GetType() == config.SoloConsensusType {
		bSynched = true
	}

	if !bSynched {
		currentHeight := chain.GetBlockHeight()
		peerMaxBlkHeight := chain.GetPeerMaxBlkHeight()
		chain.log.Debugf("Current height %d and peerMaxBlockHeight %d", currentHeight, peerMaxBlkHeight)
		if len(chain.peerList.Peers) != 0 && currentHeight >= peerMaxBlkHeight {
			bSynched = true
		}
	}

	msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, bSynched))
}

// TODO
func (chain *BlockChain) getContractTotal(msg *mq.Message) {

}

// TODO
func (chain *BlockChain) getTxTotal(msg *mq.Message) {

}

func (chain *BlockChain) getMaxBlockNumber(msg *mq.Message) {
	maxBlockNumber, err := chain.ProcessGetMaxBlockNumber()
	if err != nil {
		chain.log.Error("ProcessGetMaxBlockNumber", "err", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, maxBlockNumber))
	}
}

func (chain *BlockChain) getMaxBlockSequence(msg *mq.Message) {
	maxSequence, err := chain.ProcessGetMaxBlockSequence()
	if err != nil {
		log.Error("ProcessGetMaxBlockSequence", "err", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, maxSequence))
	}
}

func (chain *BlockChain) getMaxExecutedBlockNumber(msg *mq.Message) {
	maxExecutedBlkNum, err := chain.ProcessGetMaxExecutedBlockNumber()
	if err != nil {
		chain.log.Error("ProcessGetMaxExecutedBlockNumber", "err", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, maxExecutedBlkNum))
	}
}

func (chain *BlockChain) storeCheckedBlockTopic(msg *mq.Message) {
	blkCheckMsgBytes := msg.Data.([]byte)
	chain.blockStore.SaveTopicInfo(types.GetEventName(types.EventStoreCheckedBlockTopic), blkCheckMsgBytes)
}

func (chain *BlockChain) getCheckedBlockTopic(msg *mq.Message) {
	topicBytes, err := chain.blockStore.GetTopicInfo(types.GetEventName(types.EventStoreCheckedBlockTopic))
	if err != nil {
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		//chainchain.log.Debug("ProcGetBlockDetailsMsg", "success", "ok")
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, topicBytes))
	}
}

func (chain *BlockChain) storeReceivedBlockTopic(msg *mq.Message) {
	blkCheckMsgBytes := msg.Data.([]byte)

	chain.blockStore.SaveTopicInfo(types.GetEventName(types.EventStoreReceivedBlockTopic), blkCheckMsgBytes)
}

func (chain *BlockChain) getReceivedBlockTopic(msg *mq.Message) {
	topicBytes, err := chain.blockStore.GetTopicInfo(types.GetEventName(types.EventStoreReceivedBlockTopic))
	if err != nil && err != types.ErrNotFound {
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		//chainchain.log.Debug("ProcGetBlockDetailsMsg", "success", "ok")
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, topicBytes))
	}
}

func (chain *BlockChain) storeElectionContext(msg *mq.Message) {
	electionContextBytes := msg.Data.([]byte)
	chain.blockStore.SaveTopicInfo(types.GetEventName(types.EventStoreElectionContext), electionContextBytes)
}

func (chain *BlockChain) getElectionContext(msg *mq.Message) {
	electionContextBytes, err := chain.blockStore.GetTopicInfo(types.GetEventName(types.EventStoreElectionContext))
	if err != nil {
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, electionContextBytes))
	}
}

func (chain *BlockChain) storeRoundMinersInfo(msg *mq.Message) {
	roundMinerBytes := msg.Data.([]byte)
	var roundMinerInfo = &dpos.RoundMiners{}
	err := proto.Unmarshal(roundMinerBytes, roundMinerInfo)
	if err != nil {
		log.Errorf("store round miner info failed, err: %s", err.Error())
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
		return
	}

	var kv = &pbcommon.KeyValue{}
	kv.Key = ledger.GenKey(db.NamespaceLedgerRoundMinerInfo, strconv.Itoa(int(roundMinerInfo.Round)))
	kv.Value = roundMinerBytes
	chain.blockStore.ledgerDB.Set(kv.Key, kv.Value)
}

func (chain *BlockChain) getRoundMinersInfo(msg *mq.Message) {
	reqRound := msg.Data.(int)
	roundMinerBytes, err := chain.blockStore.ledgerDB.Get(ledger.GenKey(db.NamespaceLedgerRoundMinerInfo, strconv.Itoa(reqRound)))
	if err != nil {
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, err))
	} else {
		msg.Reply(chain.workerClient.NewMessage("", "", msg.Ty, roundMinerBytes))
	}
}

func (chain *BlockChain) storeCurrentBlockSequence(msg *mq.Message) {
	currentBlockSequence := msg.Data.(uint64)

	service, err := chain.blockStore.GetLedgerService()
	if err != nil {
		chain.log.Errorf("storeCurrentBlockSequence get ledger service failed, err:%s\n", err.Error())
		return
	}
	err = service.SetCurrentBlockSequence(currentBlockSequence)
	if err != nil {
		chain.log.Errorf("storeCurrentBlockSequence set current block sequence failed, err:%s\n", err.Error())
		return
	}
}

func (chain *BlockChain) getCurrentBlockSequence(msg *mq.Message) {
	var respMsg *mq.Message
	service, err := chain.blockStore.GetLedgerService()
	if err != nil {
		chain.log.Errorf("getCurrentBlockSequence get ledger service failed, err:%s\n", err.Error())
		respMsg = chain.workerClient.NewMessage("", "", types.EventQueryCurrentBlockSequence, err.Error())
		msg.Reply(respMsg)
		return
	}

	currentBlockSequence, err := service.GetCurrentBlockSequence()
	if err != nil {
		respMsg = chain.workerClient.NewMessage("", "", types.EventQueryCurrentBlockSequence, err.Error())
		return
	}

	respMsg = chain.workerClient.NewMessage("", "", types.EventQueryCurrentBlockSequence, currentBlockSequence)
	msg.Reply(respMsg)
}
